//! ## Mangling and Demangling Paths in CS2 System
//!
//! This module provides a mangling and demangling mechanism for paths in CS2 System. The
//! mangling mechanism is used to encode the path into a byte array, which can be used as
//! name of [`crate::utils::map::SharedMemMap`].

use std::path::{Component, Path};

/// The byte used for mangling paths.
pub(crate) const MANGLING_BYTE: u8 = b'$';

pub trait PathStrExt
where
    Self: AsRef<str>,
{
    /// Mangle a path string.
    fn mangle(&self) -> String;

    /// Demangle a path string.
    fn demangle(&self) -> String;
}

impl<T> PathStrExt for T
where
    T: AsRef<str>,
{
    fn mangle(&self) -> String {
        let mut mangled = String::new();

        let path: &Path = self.as_ref().as_ref();

        for (i, component) in path.components().enumerate() {
            if i > 0 {
                mangled.push(MANGLING_BYTE as char);
            }
            match component {
                Component::RootDir => {}
                Component::Normal(name) => {
                    for byte in name.as_encoded_bytes() {
                        if byte == &MANGLING_BYTE {
                            mangled.push(MANGLING_BYTE as char);
                            mangled.push(MANGLING_BYTE as char);
                        } else {
                            mangled.push(*byte as char);
                        }
                    }
                }
                Component::CurDir => mangled.push('.'),
                Component::ParentDir => mangled.push_str(".."),
                // On Unix platform, it should not contain `Component::Prefix`.
                _ => unreachable!(),
            }
        }

        mangled
    }

    fn demangle(&self) -> String {
        let mut demangled = String::new();
        let mut iter = self.as_ref().bytes();

        while let Some(byte) = iter.next() {
            if byte == MANGLING_BYTE {
                match iter.next() {
                    Some(MANGLING_BYTE) => demangled.push(MANGLING_BYTE as char),
                    Some(next) => demangled.extend(&['/', next as char]),
                    None => demangled.push('/'),
                }
            } else {
                demangled.push(byte as char)
            }
        }

        demangled
    }
}

#[cfg(test)]
mod test {
    use coverage_helper::test;
    use cs2s_testdata::*;

    use super::*;

    #[test]
    fn mangling_and_demangling() {
        for path in PATHS_ALT {
            let mangled = path.mangle();
            assert!(!mangled.contains('/'));
            assert!(mangled.contains(MANGLING_BYTE as char));
            let demangled = mangled.demangle();
            assert_eq!(Path::new(&demangled), Path::new(path));
        }
    }
}
